#include "sslclient.hpp"
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include<fcntl.h>
#include<string.h>
#include <unistd.h>
#endif
#include<stdio.h>
#include<string>
#include<openssl/err.h>

bool SSLClient::emptysendback(SSLClient*,void*){return true;}
SSLClient::SSLClient(DataPack defaultpack,TimeoutCallback timeout,void* timeoutarg,
time_t maxtime):maxtime(maxtime),sendcount(0),recvcount(0),_socket(-1),timeout(timeout),
timeoutarg(timeoutarg),defaultpack(defaultpack),ctx(0),sendpos(0),recvpos(0),pos(0),
size(0),verified(false){}
SSLClient::~SSLClient(){
	if(_socket!=-1){
		SSL_shutdown(ssl);
		#ifdef _WIN32
			closesocket(_socket);
		#else
			close(_socket);
		#endif
		SSL_free(ssl);
		SSL_CTX_free(ctx);
	}
}
size_t SSLClient::getdata(char* buffer,size_t length){
	if(pos!=size){
		_thread_begin_recv:
		auto got=std::min(size-pos,length-recvpos);
		memcpy(buffer+recvpos,cache+pos,got);
		pos+=got;
		return got;
	}
	if(length>=CACHESIZE){
		auto back=SSL_read(ssl,buffer+recvpos,length-recvpos);
		if(back>0)return back;
		return -1;
	}
	auto back=SSL_read(ssl,cache,CACHESIZE);
	if(back>0){
		pos=0;
		size=back;
		goto _thread_begin_recv;
	}
	return -1;
}
#ifdef _WIN32
int SSLClient::connecthost(const char* address,unsigned int port){
	std::string p(std::to_string(port));
	struct addrinfo hints;
	struct addrinfo *result=NULL;
	memset(&hints,0,sizeof(hints));
	hints.ai_family=AF_INET;
	hints.ai_socktype=SOCK_STREAM;
	hints.ai_protocol=IPPROTO_TCP;
	hints.ai_flags=AI_PASSIVE;
	auto res=getaddrinfo(0,p.c_str(),&hints,&result);
	if(res){
		printf("getaddrinfo error: %d\n",res);
		return -1;
	}
	auto s=socket(result->ai_family,result->ai_socktype,result->ai_protocol);
	if(s==INVALID_SOCKET){
		printf("socket create error\n");
		return -1;
	}
	ctx=SSL_CTX_new(SSLv23_client_method());
	if(!ctx){
		ERR_print_errors_fp(stdout);
		closesocket(s);
		return -1;
	}
	if(_socket!=-1)closesocket(_socket),SSL_free(ssl);
	ssl=SSL_new(ctx);
	SSL_set_fd(ssl,s);
	static unsigned long NBMODE=1;
	ioctlsocket(s,FIONBIO,&NBMODE);
	_socket=s;
	struct sockaddr_in addr{
		.sin_family=AF_INET,
		.sin_port=htons(port)
	};
	addr.sin_addr.s_addr=inet_addr(address);
	connect(s,(struct sockaddr*)&addr,sizeof(sockaddr_in));
	recordtime=time(nullptr);
	return 0;
}
#else
int SSLClient::connecthost(const char* address,unsigned int port){
	int s=socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK,IPPROTO_TCP);
	if(s==-1){
		printf("socket create error:%d\n",errno);
		return -1;
	}
	ctx=SSL_CTX_new(SSLv23_client_method());
	if(!ctx){
		ERR_print_errors_fp(stdout);
		close(s);
		return -1;
	}
	if(_socket!=-1)close(_socket),SSL_free(ssl);
	ssl=SSL_new(ctx);
	SSL_set_fd(ssl,s);
	int flags=fcntl(s,F_GETFL,0);
	fcntl(s,F_SETFL,flags|O_NONBLOCK);
	_socket=s;
	struct sockaddr_in addr{
		.sin_family=AF_INET,
		.sin_port=htons(port)
	};
	addr.sin_addr.s_addr=inet_addr(address);
	connect(s,(struct sockaddr*)&addr,sizeof(sockaddr_in));
	recordtime=time(nullptr);
	return 0;
}
#endif
void SSLClient::update(){
	if(!verified){
		if(SSL_connect(ssl)<=0){
			auto curtime=time(nullptr);
			if(curtime-recordtime>=maxtime){
				timeout(this,timeoutarg);
				pendingsend.clear();
				pendingrecv.clear();
				return;
			}
		}
	}
	else verified=true;
	auto& pendingrecv=this->pendingrecv;
	auto precv=pendingrecv.begin();
	auto curtime=time(nullptr);
	if(precv!=pendingrecv.end()){
		auto got=getdata(precv->src,precv->size);
		if(got!=-1){
			recvpos+=got;
			recordtime=curtime;
			if(recvpos==precv->size){
				recvpos=0;
				if(precv->callback(this,precv->arg)){
					pendingrecv.pop_front();
					recvcount--;
				}
			}
		}
		else if(curtime-recordtime>=maxtime){
			timeout(this,timeoutarg);
			pendingsend.clear();
			pendingrecv.clear();
			return;
		}
	}
	auto& pendingsend=this->pendingsend;
	auto psend=pendingsend.begin();
	auto& pack=this->defaultpack;
	auto size=pack.size;
	if(psend!=pendingsend.end()){
		auto back=SSL_write(ssl,psend->src+sendpos,psend->size-sendpos);
		if(back>0){
			recordtime=curtime;
			sendpos+=back;
			if(sendpos==psend->size){
				sendpos=0;
				if(psend->callback(this,psend->arg)){
					pendingsend.pop_front();
					sendcount--;
				}
			}
		}
		else if(curtime-recordtime>=maxtime){
			timeout(this,timeoutarg);
			pendingsend.clear();
			pendingrecv.clear();
			return;
		}
	}
	else if(size){
		auto back=SSL_write(ssl,(char*)pack.src+sendpos,size-sendpos);
		if(back>0){
			recordtime=curtime;
			sendpos+=back;
			if(sendpos==size){
				sendpos=0;
				pack.callback(this,pack.arg);
			}
		}
		else if(curtime-recordtime>=maxtime){
			timeout(this,timeoutarg);
			pendingsend.clear();
			pendingrecv.clear();
			return;
		}
	}
}
void SSLClient::senddata(DataPack p){
	if(!sendcount){
		auto ret=SSL_write(ssl,p.src,p.size);
		if(ret==p.size){if(p.callback(this,p.arg))return;}
		else if(ret!=-1)sendpos=ret;
	}
	pendingsend.push_back(p);
	sendcount++;
}
void SSLClient::recvdata(DataPack p){
	if(!recvcount){
		auto ret=getdata(p.src,p.size);
		if(ret==p.size){if(p.callback(this,p.arg))return;}
		else if(ret!=-1)recvpos=ret;
	}
	pendingrecv.push_back(p);
	recvcount++;
}
bool SSLClient::openlib(){
	#ifdef _WIN32
	WSADATA wsaData;
	auto res=WSAStartup(MAKEWORD(2,2),&wsaData);
	if(res){
		printf("WSA start error %d\n",res);
		return false;
	}
	#endif
	SSL_library_init();
	OpenSSL_add_all_algorithms();
	SSL_load_error_strings();
	return true;
}
void SSLClient::closelib(){
	#ifdef _WIN32
	WSACleanup();
	#endif
}
size_t SSLClient::getsent(){return sendpos;}
size_t SSLClient::getrecv(){return recvpos;}